package com.pic.mgt.syushin.executer.bucket;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.time.DateUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DuplicateKeyException;

import com.pic.mgt.IgxStatus;
import com.pic.mgt.dao.IBusinessListMgtDao;
import com.pic.mgt.dao.IBusinessMgtDao;
import com.pic.mgt.dao.syushin.ISyushinBucketMgtDao;
import com.pic.mgt.dao.syushin.ISyushinBusinessMgtDao;
import com.pic.mgt.dao.syushin.ISyushinBusinessTargetActionDao;
import com.pic.mgt.dao.syushin.SyushinBasicBusinessTargetActionInfoBean;
import com.pic.mgt.executer.SimpleConfirmationFileMaker;
import com.pic.mgt.file.io.FileBuilder;
import com.pic.mgt.file.io.FileOperation;
import com.pic.mgt.log.IgxLog;
import com.pic.mgt.manager.business.ApInfoBean;
import com.pic.mgt.manager.business.BasicBusinessInfoBean;
import com.pic.mgt.manager.business.BusinessManager;
import com.pic.mgt.manager.business.LatestBucketInfoBean;
import com.pic.mgt.monitor.parser.ConfirmtaionFileForTxFileParser;
import com.pic.mgt.syushin.SyushinBusinessUnitFactory;
import com.pic.mgt.syushin.executer.bucket.backup.BucketBackup;
import com.pic.mgt.syushin.executer.tar.TarFile;
import com.pic.mgt.syushin.executer.tar.TarFileException;
import com.pic.mgt.syushin.executer.tar.TarFileListener;
import com.pic.mgt.utils.DateConvertUtils;
import com.pic.mgt.utils.FilePathUtils;
import com.pic.mgt.utils.LogUtils;
import com.pic.mgt.utils.NumericUtils;

// (送多方修改)
public final class SimpleSyushinBucketProcess implements SyushinBucketProcess, TarFileListener{
	private BusinessManager businessManager;
	private IBusinessListMgtDao businessListMgtDao;
	private ISyushinBucketMgtDao syushinBucketMgtDao;
	private IBusinessMgtDao businessMgtDao;
	private ISyushinBusinessTargetActionDao syushinBusinessTargetActionDao;
	private FilenameFilter bizFilenameSuffixFilter;
	private String bizConfirmationFilenameSuffix;
	private String messageConfirmationFilenameSuffix;
	private String syushinBucketTmpPathStr;
	private String syushinOutboxBucketPathStr;
	private File syushinOutboxBucketPath;
	private TarFile tarFile;
	private FileOperation fileOperation;
	private FileBuilder oneLineContentFileBuilder;
	private BucketBackup bucketBackup;
	
	private ISyushinBusinessMgtDao syushinBusinessMgtDao;
	
	public SimpleSyushinBucketProcess(TarFile tarFile) {
		this.tarFile = tarFile;
		
		this.tarFile.setGZipCompress(false);
		this.tarFile.setTarFileListener(this);
	}

	@Override
	public void perform(String businessName) {
		// 檢查是否為有效的業務名稱
		if (!businessManager.isEffectiveBusiness(businessName)) {
			// ********************** Log **********************
			IgxLog.messageLog.error(this, "SUDA-296 Business '{}' is ineffective.",
					businessName);
			// ********************** Log **********************
			return;
		}
		
		// 檢查是否為 Batch 型態的業務
		BasicBusinessInfoBean basicBusinessInfoBean = businessManager.getBasicBusinessInfoBean(businessName);
		if (!basicBusinessInfoBean.getBusinessType().equals(SyushinBusinessUnitFactory.BATCH_SYUSHIN)) {
			// ********************** Log **********************
			IgxLog.messageLog.error(this, "SUDA-297 Business '{}' is not batch type.",
					businessName);
			// ********************** Log **********************
			return;
		}
		
		// ================= 以上為基本驗證,有誤則會停止不會往下做 ============================
		
		synchronized (basicBusinessInfoBean) { // 同一業務同一時間只能有一個thread在進行
			File bucketFile = null; // 本次將要打包的檔案
			try {
				/*
				 * (1) 檢查前一次的打包狀態(若有錯則必須進行恢復動作)
				 * (2) 產生當前要 bucket 的檔案名稱
				 */
				
				Date todayDate = new Date(); // 今天的日期
				Date bucketDate; // 本次將要打包的日期時間 (不能用來做為資料庫新增時記錄打包的時間點)
				int bucketNo; // 本次將要打包的序號
				
				// (1) 檢查前一次的打包狀態(若有錯則必須進行恢復動作)
				
				// 取出上一次打包狀態資訊
				// throws BucketProcessException
				LatestBucketInfoBean latestBucketInfoBean = getLatestBucketInfoBean(businessName);
				
				// 取得上次打包時間
				Date latestBuckeDate = latestBucketInfoBean.getLatestBucketTime();
				if (null == latestBuckeDate) {
					// 若上次bucket時間為null，表示是第一次做bucket
					latestBuckeDate = new Date();
				}

				// 取得上次打包狀態
				String latestBucketStatus = latestBucketInfoBean.getLatestBucketStatus();
				if (null == latestBucketStatus) {
					// 若上次bucket狀態為null，表示是第一次做bucket
					latestBucketStatus = IgxStatus.BUCKET_COMPLETED;
				}
				
				// 取得上次打包序號
				int latestBucketNo = latestBucketInfoBean.getLatestBucketNo();
				if (latestBucketNo < 1) {
					// 若上次bucket no 小於0，表示是第一次做bucket
					latestBucketNo = 0;
				}
				
				// 判斷是換日
				boolean isDaySwitch = !DateUtils.isSameDay(todayDate, latestBuckeDate);

				if (isLatestBucketFailed(latestBucketStatus)) {
					// 若上次打包是失敗的情況 
					
					if (isDaySwitch) { // 已換日
						// bucket no 從1開始
						bucketNo = 1;
						bucketDate = todayDate;
					}
					else { // 未換日
						// 即繼續使用上次的 bucket no進行打包動作
						bucketNo = latestBucketNo;
						bucketDate = latestBuckeDate;
					}
					
					// ********************** Log **********************
					IgxLog.messageLog.info(this,
									"Last bucket check: Last bucket no '{}' (Business: '{}') failed. The batch files in previous failed bucket will be repackaged in this time. (Bucket No: {})",
									latestBucketNo, businessName, bucketNo);
					// ********************** Log **********************
				}
				else {
					if (isDaySwitch) { // 已換日
						// bucket no 從1開始
						bucketNo = 1;
						bucketDate = todayDate;
					}
					else { // 未換日
						// 上次的 bucket no 加1，則為本次打包動作的 bucket no
						bucketNo = latestBucketNo + 1;
						bucketDate = latestBuckeDate;
					}
					
					/*
					 *  上次打包"動作"是完成的情況下，尚要再判斷二種情況
					 *  (1) 被打包的 batch檔是否已都被移除
					 *  (2) 打包出來的 bucket file 是否移到 ftp outbox 底下
					 *  若以上情況有未完成的，則要先把它做完，才能再繼續做當
					 *  前的打包動作。
					 */
					
					// 先依照 bucket no 取得上次打包的 bucket file　名稱
					File lastBucketFile = createBucketFile(
							businessName,
							latestBuckeDate,
							latestBucketNo);
					// 取得上次打包 bucket file 的確認檔
					File lastConfirmationFileForBucketFile = ConfirmtaionFileForTxFileParser.getConfirmationFile(lastBucketFile);
					
					if (latestBucketStatus.equals(IgxStatus.BUCKET_ACTION_FINISHED)) {
						/*
						 *  表示 上次 bucket 的動作完成，但還沒將 batch 檔案移除,也沒有將 bucket file
						 *  移到 syushin bucket outbox 中。(須要繼續完成這二個動作)
						 */
						
						// ********************** Log **********************
						IgxLog.messageLog.info(this,
										"Last bucket check: Last bucket no '{}' (Business: '{}') has not been completed yet. Continue processing the following step for bucket '{}' (Business: '{}').",
										latestBucketNo,
										businessName,
										latestBucketNo,
										businessName);
						// ********************** Log **********************
				
						// 找出上次打包到該 bucket 的batch檔案清單
						List<File> lastBatchFileList = getLatestBatchFileList(
														businessName, lastBucketFile.getName());
						
						// 移除上次已被打包過的  batch 檔案
						removeBatchFilesWhichHasBeenPackaged(businessName, lastBatchFileList);
						
						// 複製 bucket 檔案至 FTP OUTBOX的各AP 目錄中
						List<ApInfoBean> apInfoBeanList = this.businessManager.getBasicBusinessInfoBean(businessName).getSyushinBusinessApList();
						synchronized(apInfoBeanList) {
							for (ApInfoBean apInfoBean : apInfoBeanList) {
								copyBucketToSyushinOutbox(businessName,
														  lastBucketFile,
														  lastConfirmationFileForBucketFile,
										  				  apInfoBean);
							}
						}
						
						// 刪除 Bucket 檔 和 確認檔
						removeBucketRelatedFiles(lastBucketFile, lastConfirmationFileForBucketFile);
						
						// 更新 business list mgt 資料庫資料 
						// throws DataAccessException
						updateBusinessListMgtByBusinessName(businessName,
														    IgxStatus.BUCKET_COMPLETED);
						
						// ********************** Log **********************
						IgxLog.messageLog.info(this,
										"Last bucket check: Continue processing the following step for bucket '{}' (Business: '{}') completed.",
										latestBucketNo,
										businessName,
										latestBucketNo,
										businessName);
						// ********************** Log **********************
					}
					else if (latestBucketStatus.equals(IgxStatus.BUCKET_REMOVED_BATCH_FILES)) {
						/*
						 *  表示 上次 bucket 的動作完成，被打包的 batch 檔也刪除完成，但還沒將 bucket file
						 *  移到 syushin bucket outbox 中。(須要繼續完成這一個動作)
						 */
						
						// ********************** Log **********************
						IgxLog.messageLog.info(this,
										"Last bucket check: Last bucket no '{}' (Business: '{}') has not been completed yet. Continue processing the following step for bucket '{}' (Business: '{}').",
										latestBucketNo,
										businessName,
										latestBucketNo,
										businessName);
						// ********************** Log **********************
					
						boolean isLastBucketFileExists = lastBucketFile.exists();
						boolean isLastConfirmationFileForBucketFileExists = lastConfirmationFileForBucketFile.exists();
						if (isLastBucketFileExists && isLastConfirmationFileForBucketFileExists) {
							// 如果Bucket檔和它的OK檔都存在，則要對每一個 AP Folder 作檢查，完成接下來的動作
							
							// 複製 Bucket 到未完成的  AP Folder 中
							List<ApInfoBean> apInfoBeanList = this.businessManager.getBasicBusinessInfoBean(businessName).getSyushinBusinessApList();
							synchronized(apInfoBeanList) {
								for (ApInfoBean apInfoBean : apInfoBeanList) {
									// 取得應該複製至 AP Folder的  交易檔,確認檔 和 訊息檔
									File copiedTxFileUnderApFolder = new File(apInfoBean.getApFolder().getAbsolutePath(),
																			  lastBucketFile.getName());
									File copiedConfirmationFileForTxFileUnderApFolder = new File(copiedTxFileUnderApFolder.getAbsolutePath() + this.bizConfirmationFilenameSuffix);
									File messageFileForTxFileUnderApFolder = new File(copiedTxFileUnderApFolder.getAbsolutePath() + this.messageConfirmationFilenameSuffix);
									
									if (!copiedConfirmationFileForTxFileUnderApFolder.exists()) {
										if (!messageFileForTxFileUnderApFolder.exists()) {
											SyushinBasicBusinessTargetActionInfoBean syushinBasicBusinessTargetActionInfoBean
												= this.syushinBusinessTargetActionDao.findSyushinBusinessTargetActionInfoByFileNameAndApName(
														lastBucketFile.getName(), apInfoBean.getApName());
											if (null == syushinBasicBusinessTargetActionInfoBean ||
												null == syushinBasicBusinessTargetActionInfoBean.getLastTransactionTime() ||
												(syushinBasicBusinessTargetActionInfoBean.getCreatingTime().after(syushinBasicBusinessTargetActionInfoBean.getLastTransactionTime()))) {
												// 如果是上列這些情形,表示檔案都還沒複製完成到該 AP Foler ,所以要整個重新複製過去
												copyBucketToSyushinOutbox(businessName,
																		  lastBucketFile,
																		  lastConfirmationFileForBucketFile,
																		  apInfoBean);
											}
										}
										else {
											// message 檔都存在了,表示之前也已經複製完成了,不用再複製
										}
									}
									else {
										// 確認檔存在了,只是還沒被取走,不用再複製了
									}
								}
							}
							
							// 刪除 Bucket 檔 和 確認檔
							removeBucketRelatedFiles(lastBucketFile, lastConfirmationFileForBucketFile);
							
							// 更新 business list mgt 資料庫資料 
							// throws DataAccessException
							updateBusinessListMgtByBusinessName(businessName,
															    IgxStatus.BUCKET_COMPLETED);
						}
						else { // 刪除 Bucket 檔和它的確認的
							if (isLastBucketFileExists) {
								try {
									this.fileOperation.deleteFile(lastBucketFile);
								} catch (FileNotFoundException e) {
									// do nothing
								} catch (IOException e) {
									throw new BucketProcessException(
											"SUDA-346 Removing bucket file '" + lastBucketFile.getAbsolutePath() +
											"' occured an I/O error.");
								}
							}
							if (isLastConfirmationFileForBucketFileExists) {
								try {
									this.fileOperation.deleteFile(lastConfirmationFileForBucketFile);
								} catch (FileNotFoundException e) {
									// do nothing 
								} catch (IOException e) {
									throw new BucketProcessException(
											"SUDA-346 Removing confirmation file '" + lastConfirmationFileForBucketFile.getAbsolutePath() +
											"' for bucket occured an I/O error.");
								}
							}
							// 更新 business list mgt 資料庫資料 
							// throws DataAccessException
							updateBusinessListMgtByBusinessName(businessName,
															    IgxStatus.BUCKET_COMPLETED);
						}
						
						// ********************** Log **********************
						IgxLog.messageLog.info(this,
										"Last bucket check: Continue processing the following step for bucket '{}' (Business: '{}') completed.",
										latestBucketNo,
										businessName,
										latestBucketNo,
										businessName);
						// ********************** Log **********************
					}
					else {
						// 表示上次的 bucket 動作正常完成
						
					}
				}
				
				
				// 開始進行本次打包的動作
				
				// (1) 先產生當前要 bucket 的檔案
				// FIXME 是否這裡要 catch exception
				bucketFile = createBucketFile(businessName, bucketDate, bucketNo);
				
				// ********************** Log **********************
				IgxLog.messageLog.info(this,
								"Packaging bucket file '{}' for business '{}'. (No:{})",
								bucketFile.getName(), businessName, bucketNo);
				// ********************** Log **********************
				
				// (2) 先更新資料庫狀態
				// throws DataAccessException
				this.updateBusinessListMgtByBusinessName(
						businessName, bucketNo, IgxStatus.BUCKET_ACTION_EXECUTING);
				
				// (3) 進行打包
				// BucketProcessException
				List<File> batchFileList =
					packageBusiness(businessName, bucketFile, bucketNo, basicBusinessInfoBean);
				
				// (4) 產生 confirmation file for bucket file
				File confirmationFileForBucketFile = 
					 createConfirmationFileForBucketFile(businessName, bucketFile);
				
				// (5) 備份 Bucket
				boolean isBackupBucketOk =
					this.bucketBackup.backupBucket(bucketFile);
				if (!isBackupBucketOk) {
					return;
				}
				
				// 更新資料庫狀態 (更新為打包已完成)
				// a. 新增 bucket_mgt 記錄
				updateBucketMgt(businessName, bucketFile.getName(), bucketNo, batchFileList.size());
				// b. 修改 business_list_mgt table資料
				updateBusinessListMgtByBusinessName(businessName, IgxStatus.BUCKET_ACTION_FINISHED);
				
				// (6) 移除 已被打包的 batch 檔
				removeBatchFilesWhichHasBeenPackaged(businessName, batchFileList);
				
				// (7) 複製 bucket 檔案至 FTP OUTBOX的各AP 目錄中
				List<ApInfoBean> apInfoBeanList = this.businessManager.getBasicBusinessInfoBean(businessName).getSyushinBusinessApList();
				synchronized(apInfoBeanList) {
					for (ApInfoBean apInfoBean : apInfoBeanList) {
						copyBucketToSyushinOutbox(businessName,
												  bucketFile,
												  confirmationFileForBucketFile,
												  apInfoBean);
					}
				}
				
				// (8) 刪除 Bucket 檔 和 確認檔
				removeBucketRelatedFiles(bucketFile, confirmationFileForBucketFile);
				
				// ********************** Log **********************
				IgxLog.messageLog.info(this,
								"Packaging bucket file '{}' for business '{}' completed. (No:{} Batch file:{})",
								bucketFile.getName(), businessName, bucketNo, batchFileList.size());
				// ********************** Log **********************
				
				// (9) 更新 business list mgt 資料庫資料 
				// throws DataAccessException
				updateBusinessListMgtByBusinessName(businessName,
												    IgxStatus.BUCKET_COMPLETED);
				
			} catch (BucketProcessException e1) {
				// ********************** Log **********************
				IgxLog.messageLog.error(this, e1.getMessage());
				// ********************** Log **********************
			} catch (DataAccessException e2) {
				// ********************** Log **********************
				IgxLog.messageDetailsLog.error(this, e2.getMessage());
				// ********************** Log **********************
				
				// ********************** Log **********************
				IgxLog.messageLog.error(this,
								"SUDA-307 Packaging bucket file '{}' for business '{}' process failed in updating database status operation.",
								bucketFile.getName(),
								businessName);
				// ********************** Log **********************
			}
		}
	}
	
	private void removeBucketRelatedFiles(File bucketFile, File confirmationFileForBucketFile) throws BucketProcessException {
		try {
			this.fileOperation.deleteFile(bucketFile);
		} catch (IOException e) {
			// ********************** Log **********************
			IgxLog.messageDetailsLog.error(this, e.getMessage());
			// ********************** Log **********************
			
			throw new BucketProcessException(
					"SUDA-346 Removing bucket file '" + bucketFile.getAbsolutePath() +
					"' occured an I/O error.");
		}
		
		try {
			this.fileOperation.deleteFile(confirmationFileForBucketFile);
		} catch (IOException e) {
			// ********************** Log **********************
			IgxLog.messageDetailsLog.error(this, e.getMessage());
			// ********************** Log **********************
			
			throw new BucketProcessException(
					"SUDA-346 Removing confirmation file '" + confirmationFileForBucketFile.getAbsolutePath() +
					"' for bucket occured an I/O error.");
		}
	}
	
	private List<File> getLatestBatchFileList(String businessName, String bucketFilename) throws BucketProcessException {
		ISyushinBusinessMgtDao syushinBusinessMgtDao = (ISyushinBusinessMgtDao)this.businessMgtDao;
		
		List<File> latestBatchFileList;
		try {
			// 從資料庫取得被打包進 該bucket file的檔案名稱清單
			// throws DataAccessException
			List<String> latestBatchFilenameList = 
					syushinBusinessMgtDao.findTargetFileByBucketFilename(bucketFilename);
			
			// 轉為成為 File List
			
			latestBatchFileList = new LinkedList<File>();
			File syushinBusinessBucketTmpPath = new File(this.syushinBucketTmpPathStr, businessName);
			for (String latestBatchFilename : latestBatchFilenameList) {
				latestBatchFileList.add(new File(syushinBusinessBucketTmpPath.getAbsolutePath(),
									    latestBatchFilename));
			}
			
			return latestBatchFileList;
		} catch (DataAccessException e) {
			// ********************** Log **********************
			IgxLog.messageDetailsLog.error(this, e.getMessage());
			// ********************** Log **********************
			
			throw new BucketProcessException(
					"SUDA-299 Getting last batch files list from database failed.");
		}
	}
	
	/**
	 * 複製 Bucket檔和 Bucket的確認檔到 AP 的目錄中
	 * @param businessName
	 * @param bucketFile
	 * @param confirmationFileForBucketFile
	 * @param apName
	 * @throws BucketProcessException
	 * @throws DataAccessException
	 */
	private void copyBucketToSyushinOutbox(String businessName, File bucketFile, File confirmationFileForBucketFile, ApInfoBean apInfoBean)
							throws BucketProcessException, DataAccessException {
		// 複製前先新增資料庫記錄至 syushin business target action table
		// throws DataAccessException
		try {
			insertDataToSyushinBusinessTargetActionTable(businessName, bucketFile, apInfoBean.getApName());
		} catch(DuplicateKeyException e1){
			/*
			 *  若是有重覆的資料已在 table 中,則忽略不須做錯誤處理。
			 *  原因是上次的 Bucket 動作在某些步驟失敗，但資料已新增至 table中，
			 *  而本次在對上次打包錯誤做接續處理時會產生重覆新增。
			 */
		} catch (DataAccessException e) {
			// ********************** Log **********************
			IgxLog.messageDetailsLog.error(this, e.getMessage());
			// ********************** Log **********************

			throw e;
		}
		
		try {
			// 複製  bucket file
			try {
				// throws IOException
				this.fileOperation.copyFileToFolder(bucketFile, apInfoBean.getApFolder());
				
				// ********************** Log **********************
				if (IgxLog.messageDetailsLog.isDebugMode()) {
					IgxLog.messageDetailsLog.debug(this,
									"Copying bucket file '{}' to syushin bucket outbox '{}' completed.",
									bucketFile.getAbsolutePath(),
									apInfoBean.getApFolder().getAbsolutePath());
				}
				// ********************** Log **********************
			} catch (FileNotFoundException e) {
				// ********************** Log **********************
				IgxLog.messageDetailsLog.error(this, e.getMessage());
				// ********************** Log **********************

				throw e;
			}
			
			
			// 產生 confirmation file for bucket file
			
			// 取得將要產生在 syushin bucket outbox 底下的Bucket確認檔
			File confirmationFileForBucketFileUnderBucketOutbox = new File(apInfoBean.getApFolder().getAbsolutePath(),
															  			   confirmationFileForBucketFile.getName());
			// 取得將要產生在 syushin bucket outbox 底下Bucket確認檔的暫存檔
			File confirmationTmpFileForBucketFileUnderBucketOutbox =
				SimpleConfirmationFileMaker.getTmepConfirmationFile(confirmationFileForBucketFileUnderBucketOutbox);
			
			try {
				// 將 File OK 檔複製到 syushin outbox bucket 底下,並先命名為暫存檔名
				// throws IOException (FileNotFoundException 不用理會)
				this.fileOperation.copyFileToFile(confirmationFileForBucketFile, confirmationTmpFileForBucketFileUnderBucketOutbox);
			} catch (FileNotFoundException e) {
				// ********************** Log **********************
				IgxLog.messageDetailsLog.error(this, e.getMessage());
				// ********************** Log **********************

				throw e;
			}
			
			// 將確認暫存檔更名為正確的檔名
			// throws IOException
			this.fileOperation.moveFileToFile(confirmationTmpFileForBucketFileUnderBucketOutbox,
											  confirmationFileForBucketFileUnderBucketOutbox);
			
			// ********************** Log **********************
			if (IgxLog.messageDetailsLog.isDebugMode()) {
				IgxLog.messageDetailsLog.debug(this,
								"Generating confirmation file '{}' to syushin bucket outbox '{}' completed.",
								confirmationFileForBucketFileUnderBucketOutbox.getName(),
								apInfoBean.getApFolder().getAbsolutePath());
			}
			// ********************** Log **********************
			
			// ********************** Log **********************
			if (IgxLog.messageLog.isDebugMode()) {
				IgxLog.messageLog.debug(this,
								"Copying bucket '{}' to syushin bucket outbox '{}' completed.",
								bucketFile.getName(),
								apInfoBean.getApFolder().getAbsolutePath());
			}
			// ********************** Log **********************
		} catch (IOException e1) {
			// ********************** Log **********************
			IgxLog.messageDetailsLog.error(this, e1.getMessage());
			// ********************** Log **********************
			
			/*
			 * 這裡不要更新資料庫內的記錄狀態為失敗。因為整個打包動作已經完成，
			 * 只是在移動檔案時失敗。在下一次打包動作時，會自動判斷並繼續做完檔案
			 * 的後續搬移動作。
			 * 如果更新為狀態為失敗，反而會造成下次打包時，因自動判斷此包有錯誤,
			 * 而對這一包整個做重新打包的動作，而遺失此包內的檔案(因為此包內的檔
			 * 案已經從暫存目錄中被移除了)。
			 */
			
			throw new BucketProcessException(
					"SUDA-302 Copying bucket file '" + bucketFile.getName() +
					"' to syushin bucket outbox occured an I/O error.");
		} 
	}
	
	private void insertDataToSyushinBusinessTargetActionTable(String businessName, File bucketFile, String apName)
																			throws DataAccessException{
		Map<String, Object> insertValuesMap = new HashMap<String, Object>();
		
		insertValuesMap.put(this.syushinBusinessTargetActionDao._fileName, bucketFile.getName());
		insertValuesMap.put(this.syushinBusinessTargetActionDao._fileSize, bucketFile.length());
		insertValuesMap.put(this.syushinBusinessTargetActionDao._unitName, bucketFile.getName());
		insertValuesMap.put(this.syushinBusinessTargetActionDao._terminalId, null);
		insertValuesMap.put(this.syushinBusinessTargetActionDao._apName, apName);
		
		this.syushinBusinessTargetActionDao.insertBasic(insertValuesMap);
	}
	
	private File createConfirmationFileForBucketFile(String businessName, File bucketFile) throws BucketProcessException {
		File confirmationFileForBucketFile = ConfirmtaionFileForTxFileParser.getConfirmationFile(bucketFile);
		
		try {
			/*
			 * 同步 oneLineContentFileBuilder 目的是,會有不同的業務同時執行
			 * 這個區段，這會導致資料有不一致的問題。在 append() 完後，要 buildFIle()
			 * 這之間，會有可能插入另一個業務在執行append()，這樣在 buildFile() 時，
			 * 檔案內容就會包含到其它的資料。所以要同步，讓一次只能有一個業務執行這
			 * 個區段。
			 */
			synchronized (this.oneLineContentFileBuilder) {
				this.oneLineContentFileBuilder.append(String.valueOf(bucketFile.length()));
				
				// throws IOException
				this.oneLineContentFileBuilder.buildFile(confirmationFileForBucketFile);
			}
			
			// ********************** Log **********************
			if (IgxLog.messageLog.isDebugMode()) {
				IgxLog.messageLog.debug(this,
								"Creating confirmation file '{}' for bucket file '{}' completed.",
								confirmationFileForBucketFile.getName(),
								bucketFile.getName());
			}
			// ********************** Log **********************
			
			return confirmationFileForBucketFile;
		} catch (IOException e) {
			// ********************** Log **********************
			IgxLog.messageDetailsLog.error(this, e.getMessage());
			// ********************** Log **********************
			
			throw new BucketProcessException(
					"SUDA-306 Creating confirmation file for bucket file '"
					+ bucketFile.getName() + "' occured an I/O error.");
		}
	}
	
	/**
	 * 移除已經被打包的 batch 檔。
	 * @param businessName
	 * @param batchFilenameList
	 * @throws BucketProcessException 
	 */
	private void removeBatchFilesWhichHasBeenPackaged(String businessName, List<File> batchFileList) throws BucketProcessException {
		for (File batchFile : batchFileList) {
			// 移除 batch 檔
			try {
				fileOperation.deleteFile(batchFile);
			} catch (FileNotFoundException e1) {
				// do nothing
			} catch (IOException e2) {
				// ********************** Log **********************
				IgxLog.messageDetailsLog.error(this, e2.getMessage());
				// ********************** Log **********************
				
				/*
				 * 這裡不要更新資料庫內的記錄狀態為失敗。因為整個打包動作已經完成，
				 * 只是在移除已打包檔案時失敗。在下一次打包動作時，會自動判斷並繼續做完檔案
				 * 的移除和後續動作。
				 * 如果更新為狀態為失敗，反而會造成下次打包時，因自動判斷整個做重新打包
				 * 的動作，而遺失一些已經被刪除的檔案。
				 */
				
				throw new BucketProcessException("SUDA-300 Removeing batch files failed after taring file process completed.");
			} 
			
			// 移除 batch 檔的確認檔
			try {
				fileOperation.deleteFile(ConfirmtaionFileForTxFileParser.getConfirmationFile(batchFile));
			} catch (FileNotFoundException e1) {
				// do nothing
			} catch (IOException e2) {
				// ********************** Log **********************
				IgxLog.messageDetailsLog.error(this, e2.getMessage());
				// ********************** Log **********************
				
				/*
				 * 這裡不要更新資料庫內的記錄狀態為失敗。因為整個打包動作已經完成，
				 * 只是在移除已打包檔案時失敗。在下一次打包動作時，會自動判斷並繼續做完檔案
				 * 的移除和後續動作。
				 * 如果更新為狀態為失敗，反而會造成下次打包時，因自動判斷整個做重新打包
				 * 的動作，而遺失一些已經被刪除的檔案。
				 */
				
				throw new BucketProcessException("SUDA-300 Removeing batch files failed after taring file process completed.");
			} 
			
			// ********************** Log **********************
			if (IgxLog.messageDetailsLog.isDebugMode()) {
				IgxLog.messageDetailsLog.debug(this,
						"Removing batch file '{}' completed.", batchFile
								.getAbsolutePath());
			}
			// ********************** Log **********************
		}
		
		// ********************** Log **********************
		if (IgxLog.messageLog.isDebugMode()) {
			IgxLog.messageLog.debug(this,
							"Removing batch files completed after bucket file process finish.");
		}
		// ********************** Log **********************
		
		try {
			// 更新資料庫狀態
			this.updateBusinessListMgtByBusinessName(businessName,
													 IgxStatus.BUCKET_REMOVED_BATCH_FILES);
		} catch (DataAccessException e) {
			// ********************** Log **********************
			IgxLog.messageDetailsLog.error(this, e.getMessage());
			// ********************** Log **********************
			
			throw new BucketProcessException("SUDA-301 Removeing batch files failed in final database update step.");
		}
	}
	
	private boolean isLatestBucketFailed(String latestBucketStatus) {
		if (latestBucketStatus.equals(IgxStatus.BUCKET_ACTION_EXECUTING)) {
			return true;
		}
		else {
			return false;
		}
	}
	
	/**
	 * 根據給定的 business name 和  bucket no, 生成一個正確的 bucket file 物件回傳。
	 * @param businessName
	 * @param bucketNo
	 * @return
	 */
	private File createBucketFile(String businessName,Date bucketDate, int bucketNo) {
		// 取得  bucket 檔名的 月和日 字串
		String monthAndDayStringForBucketFilename = DateConvertUtils.dateToMMddString(bucketDate);
		
		// 取得 bucket 檔名的 序號字串
		String noStringForBucketFilename = NumericUtils.convertNumberTo_0000_String(bucketNo);
		
		// 取得 bucket filename 字串
		String bucketFilename = businessName +
								SyushinBusinessUnitFactory.BATCH_SYUSHIN +
								"_" +
								monthAndDayStringForBucketFilename +
								noStringForBucketFilename;
		
		return FilePathUtils.multiPathConcat(this.syushinBucketTmpPathStr,
											 businessName,
											 SyushinBucketProcess.bucketNameUnderBusinessFolder,
											 bucketFilename);
	}
	
	/**
	 * 將檔案打包成為 tar 檔。若成功則不會有例外拋出。
	 * @param businessName
	 * @param bucketFile
	 * @param bucketNo
	 * @param basicBusinessInfoBean
	 * @return 已被打包的檔案 List
	 * @throws BucketProcessException
	 */
	private List<File> packageBusiness(String businessName, File bucketFile,
								 int bucketNo, BasicBusinessInfoBean basicBusinessInfoBean)
								 throws BucketProcessException {
		// (1) 取得 syushin bucket tmp 底下的所有檔案 list
		File syushinBusinessBucketTmpPath = new File(this.syushinBucketTmpPathStr, businessName);
		File[] confirmationBatchFiles;
		synchronized (basicBusinessInfoBean.getBusinessName()) {
			/*
			 * 同歩的目的是要避免在list要打包的檔案清單同時,SyushinBatchBusinessUnitExecuter
			 * 又將檔案移入此暫存目錄中。
			 */
			// 取得 batch 檔的確認檔陣列
			confirmationBatchFiles = syushinBusinessBucketTmpPath.listFiles(this.bizFilenameSuffixFilter);
		}
		// 如果 confirmationBatchFiles 回傳為 null 表示該暫存目錄不存在
		if (null == confirmationBatchFiles) {
			throw new BucketProcessException(
					"SUDA-304 The business '" + businessName + "' bucket tmp folder '" +
					syushinBusinessBucketTmpPath.getAbsolutePath() + "' donesn't exist.");
		}
		
		/*
		 *  轉換為 List
		 *  (1) 過濾掉目錄,只留檔案才是要打包的
		 */
		List<File> batchFileList = new LinkedList<File>();
		for (File confirmationBatchFile : confirmationBatchFiles) {
			if (confirmationBatchFile.isFile()) {
				// 取得 Batch 檔放入 List 中
				batchFileList.add(ConfirmtaionFileForTxFileParser.getTransactionFile(confirmationBatchFile));
			}
		}
		
		//  (2) 將檔案打包成為 tar 檔
		try {
			this.tarFile.tarFile(batchFileList,
								 bucketFile.getParent(),
								 bucketFile.getName());
			
		} catch (TarFileException e1) {
			// ********************** Log **********************
			LogUtils.error(this, IgxLog.messageDetailsLog, e1);
			// ********************** Log **********************
			
			throw new BucketProcessException(
					"SUDA-305 Taring file '" + bucketFile.getName() + "' for business '" + businessName + "' failed.",
					e1);
		} 
		
		// ********************** Log **********************
		IgxLog.messageLog.info(this,
						"Taring batch files under tmp folder '{}' to bucket file '{}' completed.",
						syushinBusinessBucketTmpPath.getAbsolutePath(),
						bucketFile);
		// ********************** Log **********************
		
		return batchFileList;
	}
	
	public void setMessageConfirmationFilenameSuffix(
			String messageConfirmationFilenameSuffix) {
		this.messageConfirmationFilenameSuffix = messageConfirmationFilenameSuffix;
	}

	/**
	 * 新增資料至 bucket mgt table
	 * @param businessName
	 * @param bucketFilename
	 * @param bucketTime
	 * @param bucketNo
	 * @throws DataAccessException
	 */
	private void updateBucketMgt(String businessName, String bucketFilename,
								 int bucketNo, int batchFileAmount) throws DataAccessException {
		Map<String ,Object> insertValuesMap = new HashMap<String, Object>();
		
		insertValuesMap.put(this.syushinBucketMgtDao._businessName, businessName);
		insertValuesMap.put(this.syushinBucketMgtDao._bucketFilename, bucketFilename);
		insertValuesMap.put(this.syushinBucketMgtDao._bucketTime, new Date());
		insertValuesMap.put(this.syushinBucketMgtDao._bucketNo, bucketNo);
		insertValuesMap.put(this.syushinBucketMgtDao._batchFileAmount, batchFileAmount);
		
		try {
			// throws DataAccessException
			this.syushinBucketMgtDao.insertBasic(insertValuesMap);
		} catch (DuplicateKeyException e1) {
			/*
			 *  若是有重覆的資料已在 table 中,則忽略不須做錯誤處理。(只需更新 bucket time為現在時間)
			 *  原因是上次的 Bucket 動作在某些步驟失敗，但資料已新增至 table中，
			 *  而本次在對上次打包錯誤做接續處理時會產生重覆新增。
			 */
			
			try {
				// throws DataAccessException
				this.syushinBucketMgtDao.updateBucketTimeAndBatchFileAmountByBucketFilename(
						new Timestamp(new Date().getTime()), batchFileAmount, bucketFilename);
			} catch (DataAccessException e) {
				// ********************** Log **********************
				IgxLog.messageDetailsLog.error(this, e.getMessage());
				// ********************** Log **********************

				throw e;
			}
		} catch (DataAccessException e2) {
			throw e2;
		}
	}
	
	/**
	 * 更新 business_list_mgt table.
	 * (更新欄位: latest bucket no, latest bucket status, latest bucket time)
	 * @param businessName
	 * @param latestBucketNo
	 * @param latestBucketStatus
	 * @param latestBucketTime
	 * @throws DataAccessException
	 */
	private void updateBusinessListMgtByBusinessName(String businessName, int latestBucketNo,
									   String latestBucketStatus) throws DataAccessException {
		this.businessListMgtDao.updateLatestBasicBucketInfoByBusinessName(
				latestBucketNo, latestBucketStatus, new Date(), businessName);
	}
	
	/**
	 * 更新 business_list_mgt table. (更新欄位: latest bucket status)
	 * @param businessName
	 * @param latestBucketStatus
	 * @throws DataAccessException
	 */
	private void updateBusinessListMgtByBusinessName(String businessName, String latestBucketStatus)
										 throws DataAccessException {
		this.businessListMgtDao.updateLatestBucketStatusByBusinessName(
												latestBucketStatus, businessName);
	}
	
	private LatestBucketInfoBean getLatestBucketInfoBean(String businessName) throws BucketProcessException {
		try {
			// throws DataAccessException
			LatestBucketInfoBean latestBucketInfoBean =
				this.businessListMgtDao.findLatestBucketInfoByBusinessName(businessName);
			if (null == latestBucketInfoBean) {
				// 若資料庫找不到該業務上一次打包狀態資訊,表示已沒有該業務.
				throw new BucketProcessException(
						"SUDA-296 Business '" + businessName + "' is ineffective.");
			}
			return latestBucketInfoBean;
		} catch (DataAccessException e) {
			// ********************** Log **********************
			IgxLog.messageDetailsLog.error(this, e.getMessage());
			// ********************** Log **********************
			
			throw new BucketProcessException(
					"SUDA-298 Getting last bucket info for business '" + businessName +
					"' from database (table: " +
					this.businessListMgtDao.getTableName() + ") failed.");
		}
	}
	
	@Override
	public void processedAnEntryNotify(File processedEntry, File tarFile) {
		// 將已被打包進去tar檔的檔案，更新其資料庫狀態
		
		// target file name 即為 unit name
		try {
			// ********************** Log **********************
			if (IgxLog.messageDetailsLog.isDebugMode()) {
				IgxLog.messageDetailsLog.debug(this,
								"Batch file '{}' has been packaged in bucket file '{}'.",
								processedEntry.getAbsolutePath(), tarFile
										.getAbsolutePath());
			}
			// ********************** Log **********************
			
			// throw DataAccessException
			syushinBusinessMgtDao.updateStatusAndBucketFilenameByUnitName(
					IgxStatus.BUSINESS_COMPLETED, tarFile.getName(), processedEntry.getName());
		} catch (DataAccessException e) {
			// ********************** Log **********************
			IgxLog.messageDetailsLog.error(this,
					"Update status to db table '{}' for unit '{}' failed.",
					this.businessMgtDao.getTableName(), processedEntry
							.getName());
			// ********************** Log **********************
			
			// 中斷 tar 動作的執行
			this.tarFile.interruptDeTarAction();
		}
	}
	
	// --- getter and setter ---

	public void setBusinessManager(BusinessManager businessManager) {
		this.businessManager = businessManager;
	}

	public void setBusinessListMgtDao(IBusinessListMgtDao businessListMgtDao) {
		this.businessListMgtDao = businessListMgtDao;
	}

	public void setSyushinBucketTmpPathStr(String syushinBucketTmpPathStr) {
		this.syushinBucketTmpPathStr = syushinBucketTmpPathStr;
	}

	public void setSyushinBusinessTargetActionDao(
			ISyushinBusinessTargetActionDao syushinBusinessTargetActionDao) {
		this.syushinBusinessTargetActionDao = syushinBusinessTargetActionDao;
	}

	public void setBizFilenameSuffixFilter(FilenameFilter bizFilenameSuffixFilter) {
		this.bizFilenameSuffixFilter = bizFilenameSuffixFilter;
	}

	public void setBusinessMgtDao(IBusinessMgtDao businessMgtDao) {
		this.businessMgtDao = businessMgtDao;
		
		this.syushinBusinessMgtDao = (ISyushinBusinessMgtDao)businessMgtDao;
	}

	public void setFileOperation(FileOperation fileOperation) {
		this.fileOperation = fileOperation;
	}

	public void setSyushinBucketMgtDao(ISyushinBucketMgtDao syushinBucketMgtDao) {
		this.syushinBucketMgtDao = syushinBucketMgtDao;
	}

	public void setSyushinOutboxBucketPathStr(String syushinOutboxBucketPathStr) {
		this.syushinOutboxBucketPathStr = syushinOutboxBucketPathStr;
		
		this.syushinOutboxBucketPath = new File(syushinOutboxBucketPathStr);
	}

	public void setBizConfirmationFilenameSuffix(
			String bizConfirmationFilenameSuffix) {
		this.bizConfirmationFilenameSuffix = bizConfirmationFilenameSuffix;
	}

	public void setOneLineContentFileBuilder(FileBuilder oneLineContentFileBuilder) {
		this.oneLineContentFileBuilder = oneLineContentFileBuilder;
	}

	public void setBucketBackup(BucketBackup bucketBackup) {
		this.bucketBackup = bucketBackup;
	}
}
